#ifndef CpLog_h
#define CpLog_h

#include <TObject.h>
#include <TObjArray.h>
#include <TString.h>


class CpLog: public TObject {
  public:
    CpLog();
    virtual ~CpLog();
    static CpLog* Instance() {return fgInstance;}

    enum EType_t {kFatal = 0, kError, kWarning, kInfo, kDebug, kMaxType};
    typedef void (*CpLogNotification)(EType_t type, const char* message);


    static void  EnableDebug(Bool_t enabled);
    static void  SetGlobalLogLevel(EType_t type);
    static Int_t GetGlobalLogLevel();
    static void  SetGlobalDebugLevel(Int_t level);
    static Int_t GetGlobalDebugLevel();
    static void  SetModuleDebugLevel(const char* module, Int_t level);
    static void  ClearModuleDebugLevel(const char* module);
    static void  SetClassDebugLevel(const char* className, Int_t level);
    static void  ClearClassDebugLevel(const char* className);

    static void  SetStandardOutput();
    static void  SetStandardOutput(EType_t type);
    static void  SetErrorOutput();
    static void  SetErrorOutput(EType_t type);
    static void  SetFileOutput(const char* fileName);
    static void  SetFileOutput(EType_t type, const char* fileName);
    static void  SetStreamOutput(ostream* stream);
    static void  SetStreamOutput(EType_t type, ostream* stream);
    static void  SetLogNotification(CpLogNotification pCallBack);
    static void  SetLogNotification(EType_t type, CpLogNotification pCallBack);
    static void  Flush();

    static void  SetHandleRootMessages(Bool_t on);

    static void  SetPrintType(Bool_t on);
    static void  SetPrintType(EType_t type, Bool_t on);
    static void  SetPrintModule(Bool_t on);
    static void  SetPrintModule(EType_t type, Bool_t on);
    static void  SetPrintScope(Bool_t on);
    static void  SetPrintScope(EType_t type, Bool_t on);
    static void  SetPrintLocation(Bool_t on);
    static void  SetPrintLocation(EType_t type, Bool_t on);

    static void  SetPrintRepetitions(Bool_t on);

    static void  WriteToFile(const char* name, Int_t option = 0);

    // the following public methods are used by the preprocessor macros
    // and should not be called directly
    static Bool_t IsDebugEnabled() {return fgDebugEnabled;}
    static Int_t GetDebugLevel(const char* module, const char* className);
    static void  Message(UInt_t level, const char* message,
                         const char* module, const char* className,
                         const char* function, const char* file, Int_t line);
    static void  Debug(UInt_t level, const char* message,
                       const char* module, const char* className,
                       const char* function, const char* file, Int_t line);

    static Int_t RedirectStdoutTo(EType_t type, UInt_t level, const char* module,
                                  const char* className, const char* function,
                                  const char* file, Int_t line, Bool_t print);
    static Int_t RedirectStderrTo(EType_t type, UInt_t level, const char* module,
                                  const char* className, const char* function,
                                  const char* file, Int_t line, Bool_t print);
    static void  RestoreStdout(Int_t original);
    static void  RestoreStderr(Int_t original);

    static ostream& Stream(EType_t type, UInt_t level,
                           const char* module, const char* className,
                           const char* function, const char* file, Int_t line);

  private:
    CpLog(const CpLog& log);
    CpLog& operator = (const CpLog& log);

    void           ReadEnvSettings();

    static void    RootErrorHandler(Int_t level, Bool_t abort,
                                    const char* location, const char* message);

    void           CloseFile(Int_t type);
    FILE*          GetOutputStream(Int_t type);

    UInt_t         GetLogLevel(const char* module, const char* className) const;
    void           PrintMessage(UInt_t type, const char* message,
                                const char* module, const char* className,
                                const char* function,
                                const char* file, Int_t line);

    void           PrintString(Int_t type, FILE* stream, const char* format, ...);
    void           PrintRepetitions();

    Int_t          RedirectTo(FILE* stream, EType_t type, UInt_t level,
                              const char* module, const char* className,
                              const char* function,
                              const char* file, Int_t line, Bool_t print);

    ostream&       GetStream(EType_t type, UInt_t level,
                             const char* module, const char* className,
                             const char* function, const char* file, Int_t line);

    enum {kDebugOffset = kDebug - 1};

    static CpLog* fgInstance;                 //! pointer to current instance

    static Bool_t  fgDebugEnabled;             // flag for debug en-/disabling

    UInt_t         fGlobalLogLevel;            // global logging level
    TObjArray      fModuleDebugLevels;         // debug levels for modules
    TObjArray      fClassDebugLevels;          // debug levels for classes

    Int_t          fOutputTypes[kMaxType];     // types of output streams
    TString        fFileNames[kMaxType];       // file names
    FILE*          fOutputFiles[kMaxType];     //! log output files
    ostream*       fOutputStreams[kMaxType];   //! log output streams

    Bool_t         fPrintType[kMaxType];       // print type on/off
    Bool_t         fPrintModule[kMaxType];     // print module on/off
    Bool_t         fPrintScope[kMaxType];      // print scope/class name on/off
    Bool_t         fPrintLocation[kMaxType];   // print file and line on/off

    Bool_t         fPrintRepetitions;          // print number of repetitions instead of repeated message on/off

    Int_t          fRepetitions;               //! counter of repetitions
    UInt_t         fLastType;                  //! type of last message
    TString        fLastMessage;               //! last message
    TString        fLastModule;                //! module name of last message
    TString        fLastClassName;             //! class name of last message
    TString        fLastFunction;              //! function name of last message
    TString        fLastFile;                  //! file name of last message
    Int_t          fLastLine;                  //! line number of last message
    CpLogNotification fCallBacks[kMaxType];   //! external notification callback

    ClassDef(CpLog, 1)   // class for logging debug, info and error messages
};


// module name
#ifdef _MODULE_
#define MODULENAME() _MODULE_
#else
#define MODULENAME() "NoModule"
#endif

// function name
#if defined(__GNUC__) || defined(__ICC) || defined(__ECC) || defined(__APPLE__)
#define FUNCTIONNAME() __FUNCTION__
// #elif defined(__HP_aCC) || defined(__alpha) || defined(__DECCXX)
// #define FUNCTIONNAME() __FUNC__
#else
#define FUNCTIONNAME() "???"
#endif

// redirection
#define REDIRECTSTDOUT(type, level, scope, whatever) {Int_t originalStdout = CpLog::RedirectStdoutTo(type, level, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__, kFALSE); whatever; CpLog::RestoreStdout(originalStdout);}
#define REDIRECTSTDERR(type, level, scope, whatever) {Int_t originalStderr = CpLog::RedirectStderrTo(type, level, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__, kFALSE); whatever; CpLog::RestoreStderr(originalStderr);}
#define REDIRECTSTDOUTANDSTDERR(type, level, scope, whatever) {Int_t originalStdout = CpLog::RedirectStdoutTo(type, level, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__, kFALSE); Int_t originalStderr = CpLog::RedirectStderrTo(type, level, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__, kFALSE); whatever; CpLog::RestoreStderr(originalStderr); CpLog::RestoreStdout(originalStdout);}


// debug level
#ifdef LOG_NO_DEBUG
#define CpDebugLevel() -1
#define CpDebugLevelClass() -1
#define CpDebugLevelGeneral(scope) -1
#else
#define CpDebugLevel() ((CpLog::IsDebugEnabled()) ? CpLog::GetDebugLevel(MODULENAME(), ClassName()) : -1)
#define CpDebugLevelClass() ((CpLog::IsDebugEnabled()) ? CpLog::GetDebugLevel(MODULENAME(), Class()->GetName()) : -1)
#define CpDebugLevelGeneral(scope) ((CpLog::IsDebugEnabled()) ? CpLog::GetDebugLevel(MODULENAME(), scope) : -1)
#endif

// debug messages
#ifdef LOG_NO_DEBUG
#define CpDebug(level, message)
#define CpDebugClass(level, message)
#define CpDebugGeneral(scope, level, message)
#else
/** @defn CpDebug
    @param N Debug level - always evaluated
    @param A Argument Form(including paranthesis) - the message to
    print.  Note, that @a A should contain balanced paranthesis, like
    @verbatim
      CpDebug(1, Form("Failed to decode line %d of %s", line, filename));
    @endverbatim
    The point is, if the current log level isn't high enough, as
    returned by the CpLog object, then we do not want to evalute the
    call to Form, since that is an expensive call.  We should always
    put macros like this into a @c do ... @c while loop, since that
    makes sure that evaluations are local, and that we can safely put
    a @c ; after the macro call.  Note, that @c do ... @c while loop
    and the call with extra paranthis, are an old tricks used by many
    C coders (see for example Bison, the Linux kernel, and the like).
    Christian Holm Christensen
*/
#define CpDebug(N, A) \
  do { \
    if (!CpLog::IsDebugEnabled() || \
      CpLog::GetDebugLevel(MODULENAME(), ClassName()) < N)  break; \
    CpLog::Debug(N, A, MODULENAME(), ClassName(), FUNCTIONNAME(), \
      __FILE__, __LINE__); } while (false)
//#define CpDebug(level, message) {if (CpLog::IsDebugEnabled()) CpLog::Debug(level, message, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpDebugClass(N, A) \
  do { \
    if (!CpLog::IsDebugEnabled() || \
      CpLog::GetDebugLevel(MODULENAME(), Class()->GetName()) < N)  break; \
    CpLog::Debug(N, A, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), \
      __FILE__, __LINE__); } while (false)
//#define CpDebugClass(level, message) {if (CpLog::IsDebugEnabled()) CpLog::Debug(level, message, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpDebugGeneral(scope, N, A) \
  do { \
    if (!CpLog::IsDebugEnabled() || \
      CpLog::GetDebugLevel(MODULENAME(), scope) < N)  break; \
    CpLog::Debug(N, A, MODULENAME(), scope, FUNCTIONNAME(), \
      __FILE__, __LINE__); } while (false)
//#define CpDebugGeneral(scope, level, message) {if (CpLog::IsDebugEnabled()) CpLog::Debug(level, message, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__);}
#endif

// redirection to debug
#define StdoutToCpDebug(level, whatever) REDIRECTSTDOUT(CpLog::kDebug, level, ClassName(), whatever)
#define StderrToCpDebug(level, whatever) REDIRECTSTDERR(CpLog::kDebug, level, ClassName(), whatever)
#define ToCpDebug(level, whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kDebug, level, ClassName(), whatever)
#define StdoutToCpDebugClass(level, whatever) REDIRECTSTDOUT(CpLog::kDebug, level, Class()->GetName(), whatever)
#define StderrToCpDebugClass(level, whatever) REDIRECTSTDERR(CpLog::kDebug, level, Class()->GetName(), whatever)
#define ToCpDebugClass(level, whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kDebug, level, Class()->GetName(), whatever)
#define StdoutToCpDebugGeneral(scope, level, whatever) REDIRECTSTDOUT(CpLog::kDebug, level, scope, whatever)
#define StderrToCpDebugGeneral(scope, level, whatever) REDIRECTSTDERR(CpLog::kDebug, level, scope, whatever)
#define ToCpDebugGeneral(scope, level, whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kDebug, level, scope, whatever)

// debug stream objects
#define CpDebugStream(level) CpLog::Stream(CpLog::kDebug, level, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpDebugClassStream(level) CpLog::Stream(CpLog::kDebug, level, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpDebugGeneralStream(scope, level) CpLog::Stream(CpLog::kDebug, level, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__)


// info messages
#ifdef LOG_NO_INFO
#define CpInfo(message)
#define CpInfoClass(message)
#define CpInfoGeneral(scope, message)
#else
#define CpInfo(message) {CpLog::Message(CpLog::kInfo, message, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpInfoClass(message) {CpLog::Message(CpLog::kInfo, message, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpInfoGeneral(scope, message) {CpLog::Message(CpLog::kInfo, message, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__);}
#endif

// redirection to info
#define StdoutToCpInfo(whatever) REDIRECTSTDOUT(CpLog::kInfo, 0, ClassName(), whatever)
#define StderrToCpInfo(whatever) REDIRECTSTDERR(CpLog::kInfo, 0, ClassName(), whatever)
#define ToCpInfo(whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kInfo, 0, ClassName(), whatever)
#define StdoutToCpInfoClass(whatever) REDIRECTSTDOUT(CpLog::kInfo, 0, Class()->GetName(), whatever)
#define StderrToCpInfoClass(whatever) REDIRECTSTDERR(CpLog::kInfo, 0, Class()->GetName(), whatever)
#define ToCpInfoClass(whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kInfo, 0, Class()->GetName(), whatever)
#define StdoutToCpInfoGeneral(scope, whatever) REDIRECTSTDOUT(CpLog::kInfo, 0, scope, whatever)
#define StderrToCpInfoGeneral(scope, whatever) REDIRECTSTDERR(CpLog::kInfo, 0, scope, whatever)
#define ToCpInfoGeneral(scope, whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kInfo, 0, scope, whatever)

// info stream objects
#define CpInfoStream() CpLog::Stream(CpLog::kInfo, 0, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpInfoClassStream() CpLog::Stream(CpLog::kInfo, 0, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpInfoGeneralStream(scope) CpLog::Stream(CpLog::kInfo, 0, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__)


// warning messages
#ifdef LOG_NO_WARNING
#define CpWarning(message)
#define CpWarningClass(message)
#define CpWarningGeneral(scope, message)
#else
#define CpWarning(message) {CpLog::Message(CpLog::kWarning, message, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpWarningClass(message) {CpLog::Message(CpLog::kWarning, message, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpWarningGeneral(scope, message) {CpLog::Message(CpLog::kWarning, message, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__);}
#endif

// redirection to warning
#define StdoutToCpWarning(whatever) REDIRECTSTDOUT(CpLog::kWarning, 0, ClassName(), whatever)
#define StderrToCpWarning(whatever) REDIRECTSTDERR(CpLog::kWarning, 0, ClassName(), whatever)
#define ToCpWarning(whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kWarning, 0, ClassName(), whatever)
#define StdoutToCpWarningClass(whatever) REDIRECTSTDOUT(CpLog::kWarning, 0, Class()->GetName(), whatever)
#define StderrToCpWarningClass(whatever) REDIRECTSTDERR(CpLog::kWarning, 0, Class()->GetName(), whatever)
#define ToCpWarningClass(whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kWarning, 0, Class()->GetName(), whatever)
#define StdoutToCpWarningGeneral(scope, whatever) REDIRECTSTDOUT(CpLog::kWarning, 0, scope, whatever)
#define StderrToCpWarningGeneral(scope, whatever) REDIRECTSTDERR(CpLog::kWarning, 0, scope, whatever)
#define ToCpWarningGeneral(scope, whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kWarning, 0, scope, whatever)

// warning stream objects
#define CpWarningStream() CpLog::Stream(CpLog::kWarning, 0, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpWarningClassStream() CpLog::Stream(CpLog::kWarning, 0, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpWarningGeneralStream(scope) CpLog::Stream(CpLog::kWarning, 0, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__)


// error messages
#define CpError(message) {CpLog::Message(CpLog::kError, message, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpErrorClass(message) {CpLog::Message(CpLog::kError, message, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpErrorGeneral(scope, message) {CpLog::Message(CpLog::kError, message, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__);}

// redirection to error
#define StdoutToCpError(whatever) REDIRECTSTDOUT(CpLog::kError, 0, ClassName(), whatever)
#define StderrToCpError(whatever) REDIRECTSTDERR(CpLog::kError, 0, ClassName(), whatever)
#define ToCpError(whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kError, 0, ClassName(), whatever)
#define StdoutToCpErrorClass(whatever) REDIRECTSTDOUT(CpLog::kError, 0, Class()->GetName(), whatever)
#define StderrToCpErrorClass(whatever) REDIRECTSTDERR(CpLog::kError, 0, Class()->GetName(), whatever)
#define ToCpErrorClass(whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kError, 0, Class()->GetName(), whatever)
#define StdoutToCpErrorGeneral(scope, whatever) REDIRECTSTDOUT(CpLog::kError, 0, scope, whatever)
#define StderrToCpErrorGeneral(scope, whatever) REDIRECTSTDERR(CpLog::kError, 0, scope, whatever)
#define ToCpErrorGeneral(scope, whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kError, 0, scope, whatever)

// error stream objects
#define CpErrorStream() CpLog::Stream(CpLog::kError, 0, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpErrorClassStream() CpLog::Stream(CpLog::kError, 0, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpErrorGeneralStream(scope) CpLog::Stream(CpLog::kError, 0, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__)


// fatal messages
#define CpFatal(message) {CpLog::Message(CpLog::kFatal, message, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpFatalClass(message) {CpLog::Message(CpLog::kFatal, message, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpFatalGeneral(scope, message) {CpLog::Message(CpLog::kFatal, message, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__);}

#endif
